use std::path::Path;

use anyhow::Result;
use serde::Serialize;
use serde::de::DeserializeOwned;

use crate::{
    utils::{
        const_assert::{ConstAssert, IsTrue},
        region_store::RegionStore,
    },
    voxel::{
        chunk::Chunk,
        layout::{chunk_voxel_count, region_chunk_count, region_dim},
        world::{LocateResult, RegionId},
    },
};

#[derive(Clone)]
pub enum ChunkSlot<const CBITS: u8, DATA: Clone + Serialize + DeserializeOwned + Default>
where
    [(); chunk_voxel_count::<CBITS>()]:,
    ConstAssert<{ CBITS <= 8 }>: IsTrue,
{
    Empty,
    Resident(Chunk<CBITS, DATA>),
}

pub struct Region<
    const RBITS: u8,
    const CBITS: u8,
    DATA: Clone + Serialize + DeserializeOwned + Default,
> where
    [(); region_chunk_count::<RBITS>()]:,
    [(); chunk_voxel_count::<CBITS>()]:,
    ConstAssert<{ RBITS <= 8 }>: IsTrue,
    ConstAssert<{ CBITS <= 8 }>: IsTrue,
{
    dense: Vec<ChunkSlot<CBITS, DATA>>,
    sparse: Box<[u32; region_chunk_count::<RBITS>()]>,
    store: RegionStore<RBITS, CBITS, DATA>,
    pub pos: RegionId,
}

impl<const RBITS: u8, const CBITS: u8, DATA: Clone + Serialize + DeserializeOwned + Default>
    Region<RBITS, CBITS, DATA>
where
    [(); region_chunk_count::<RBITS>()]:,
    [(); chunk_voxel_count::<CBITS>()]:,
    ConstAssert<{ RBITS <= 8 }>: IsTrue,
    ConstAssert<{ CBITS <= 8 }>: IsTrue,
{
    const R: usize = region_dim::<RBITS>();

    pub fn new(pos: RegionId, base_dir: &Path) -> Result<Self> {
        let store = RegionStore::with_region_pos(pos.0, pos.1, pos.2, base_dir)?;
        let dense = Vec::new();
        let mut sparse_vec = Vec::with_capacity(region_chunk_count::<RBITS>());
        sparse_vec.resize(region_chunk_count::<RBITS>(), 0u32);
        let sparse: Box<[u32; region_chunk_count::<RBITS>()]> = match sparse_vec.try_into() {
            Ok(b) => b,
            Err(_) => unreachable!(),
        };
        Ok(Self {
            dense,
            sparse,
            store,
            pos,
        })
    }

    #[inline]
    pub fn chunk_linear_index(x: u32, y: u32, z: u32) -> usize {
        let (x, y, z) = (x as usize, y as usize, z as usize);
        x + Self::R * (y + Self::R * z)
    }

    pub fn load_existing(&mut self) -> Result<()> {
        for i in 0..region_chunk_count::<RBITS>() {
            if self.sparse[i] != 0 {
                continue;
            }
            let len = self.store.header.chunk_lengths[i];
            if len == 0 {
                continue;
            }
            let chunk = self.store.read_chunk_at(i)?;
            self.dense.push(ChunkSlot::Resident(chunk));
            self.sparse[i] = self.dense.len() as u32;
        }
        Ok(())
    }

    /// 加载特定的 chunk，如果已存在则跳过
    pub fn load_chunk(&mut self, clx: u32, cly: u32, clz: u32) -> Result<bool> {
        let n = region_dim::<RBITS>() as u32;
        if clx >= n || cly >= n || clz >= n {
            return Ok(false);
        }

        let index = Self::chunk_linear_index(clx, cly, clz);
        if index >= region_chunk_count::<RBITS>() {
            return Ok(false);
        }

        if self.sparse[index] != 0 {
            return Ok(false);
        }

        let len = self.store.header.chunk_lengths[index];
        if len == 0 {
            return Ok(false);
        }

        let chunk = self.store.read_chunk_at(index)?;
        self.dense.push(ChunkSlot::Resident(chunk));
        self.sparse[index] = self.dense.len() as u32; // 1-based
        Ok(true)
    }

    pub fn load_chunks(&mut self, chunk_coords: &[(u32, u32, u32)]) -> Result<usize> {
        let mut loaded_count = 0;
        for &(clx, cly, clz) in chunk_coords {
            if self.load_chunk(clx, cly, clz)? {
                loaded_count += 1;
            }
        }
        Ok(loaded_count)
    }

    /// 获取或创建一个新的空 chunk
    pub fn get_or_create_chunk_mut(
        &mut self,
        clx: u32,
        cly: u32,
        clz: u32,
    ) -> Option<&mut Chunk<CBITS, DATA>> {
        let n = region_dim::<RBITS>() as u32;
        if clx >= n || cly >= n || clz >= n {
            return None;
        }

        let index = Self::chunk_linear_index(clx, cly, clz);
        if index >= region_chunk_count::<RBITS>() {
            return None;
        }

        // 先尝试从磁盘加载
        let _ = self.load_chunk(clx, cly, clz);

        // 如果 chunk 仍然不存在（磁盘上也没有），创建一个新的空 chunk
        if self.sparse[index] == 0 {
            let new_chunk = Chunk::new();
            self.dense.push(ChunkSlot::Resident(new_chunk));
            self.sparse[index] = self.dense.len() as u32; // 1-based
        }

        // 返回 chunk 的可变引用
        self.get_chunk_mut(clx, cly, clz)
    }

    #[inline]
    pub fn get_chunk_mut(
        &mut self,
        clx: u32,
        cly: u32,
        clz: u32,
    ) -> Option<&mut Chunk<CBITS, DATA>> {
        let index = Self::chunk_linear_index(clx, cly, clz);
        if index >= region_chunk_count::<RBITS>() {
            return None;
        }
        let dense_index = self.sparse[index];
        if dense_index == 0 {
            return None; // chunk 未加载
        }
        let chunk_slot = self.dense.get_mut((dense_index - 1) as usize)?; // sparse 是 1-based
        match chunk_slot {
            ChunkSlot::Empty => None,
            ChunkSlot::Resident(chunk) => Some(chunk),
        }
    }

    #[inline]
    pub fn get_chunk_ref(&self, clx: u32, cly: u32, clz: u32) -> Option<&Chunk<CBITS, DATA>> {
        let index = Self::chunk_linear_index(clx, cly, clz);
        if index >= region_chunk_count::<RBITS>() {
            return None;
        }
        let dense_index = self.sparse[index];
        if dense_index == 0 {
            return None;
        }
        match &self.dense[(dense_index - 1) as usize] {
            ChunkSlot::Empty => None,
            ChunkSlot::Resident(chunk) => Some(chunk),
        }
    }

    #[inline]
    pub fn try_get_tile_density(&self, pos: &LocateResult) -> Option<f32> {
        let chunk = self.get_chunk_ref(
            pos.chunk_local.0,
            pos.chunk_local.1,
            pos.chunk_local.2,
        )?;
        Some(chunk.get_density(
            pos.tile_local.0,
            pos.tile_local.1,
            pos.tile_local.2,
        ))
    }

    #[inline]
    pub fn get_tile_density(&self, pos: &LocateResult) -> f32 {
        let Some(chunk) =
            self.get_chunk_ref(pos.chunk_local.0, pos.chunk_local.1, pos.chunk_local.2)
        else {
            return 0.0;
        };
        chunk.get_density(pos.tile_local.0, pos.tile_local.1, pos.tile_local.2)
    }


    pub fn sparse_slot_is_empty(&self, idx: usize) -> bool {
        if idx >= self.sparse.len() { return true; }
        self.sparse[idx] == 0
    }
}
